/**
 * @file 数字相关api
 */

import { isNumber } from './types'

/**
 * 判断数字是否为整数
 * @param num
 * @returns {boolean}
 *
 * @example
 * numIsInt(1) // => true
 * numIsInt(1.1) // => false
 */
export const numIsInt = (num: number) => {
  if (!isNumber(num) || isNaN(num)) {
    return false
  }
  return ~~num === num
}

/**
 * 判断数字是否为浮点型
 * @param num
 * @returns {boolean}
 *
 * @example
 * numIsFloat(1.1) // => true
 * numIsFloat(1) // => false
 */
export const numIsFloat = (num: number) => {
  if (!isNumber(num) || isNaN(num)) {
    return false
  }
  return num % 1 != 0
}

/**
 * 返回指定范围内的随机数（整数）
 * @param {number} min 最小值
 * @param {number} max 最大值
 * @returns {number} 在范围内的随机整数
 *
 * @example
 * randomInt(5,10) // => 5 || 6 || 7 || 8 || 9 || 10
 */
export const randomInt = (min: number, max: number) => {
  if (isNaN(min) || isNaN(max)) {
    throw Error('range must be number')
  }
  if (numIsFloat(min) || numIsFloat(max)) {
    throw Error('range must be int')
  }
  if (min > max) {
    throw Error('range min must be less than range max')
  }
  return Math.floor(Math.random() * (max - min + 1)) + min
}

/**
 * 将数字四舍五入到指定的小数位数
 * @param {number} n 操作的数字
 * @param {number} decimals 精确到几位小数
 * @returns {number} 四舍五入后的数
 *
 * @example
 * round(12.555,2) // => 12.56
 */
export const round = (n: number, decimals = 0) => {
  return Number(`${Math.round(Number(`${n}e${decimals}`))}e-${decimals}`)
}

/**
 * 根据函数映射每个元素，然后返回数组的和
 * @param {Array} arr
 * @param {Function | string} fn
 * @returns {number} 映射字段元素的和
 *
 * @example
 * sumBy([{num:1},{num:2},{num:3},{num:4},{num:5}], (row)=>row.num) // => 15
 * sumBy([{num:1},{num:2},{num:3},{num:4},{num:5}], 'num') // => 15
 */
export function sumBy<T>(arr: T[], fn: (row: T) => number): number
export function sumBy<T extends object, K extends keyof T>(arr: T[], fn: K): number
export function sumBy<T extends object, K extends keyof T>(arr: T[], fn: K): number {
  return arr
    .map(typeof fn === 'function' ? fn : val => val[fn])
    .reduce((acc, val) => addNum(acc, val as unknown as number), 0)
}
/**
 * 返回数字数组中元素之和（两个或两个以上数字）
 * @param  {Array} arr 操作的数组
 * @returns {number} 数组元素之和
 *
 * @example
 * sum([1,2,3,4,5]) // => 15
 */
export const sum = (arr: number[]) => sumBy(arr, row => row)

/**
 * 将数字转化为千分位格式,可以在数字前面加上符号
 * @param {number} num
 * @param {string} mark
 * @returns {string}
 *
 * @example
 * toDecimalMark(12345674654.123,'￥') // => "￥12,345,674,654.123"
 */
export const toDecimalMark = (num: number, mark = '') =>
  num.toLocaleString('en-US').replace(/^/, mark)

/**
 * 加法运算（解决浮点数计算问题）
 * @param {number} a
 * @param {number} b
 * @returns {number} 返回正确相加后的数
 *
 * @example
 * addNum(0.3 , 0.6) // => 0.9
 */
export const addNum = (a: number, b: number) => {
  var c, d, e
  try {
    c = a.toString().split('.')[1].length
  } catch (f) {
    c = 0
  }
  try {
    d = b.toString().split('.')[1].length
  } catch (f) {
    d = 0
  }
  return (e = Math.pow(10, Math.max(c, d))), (mulNum(a, e) + mulNum(b, e)) / e
}

/**
 * 减法运算（解决浮点数计算问题）
 * @param {number} a 被减数
 * @param {number} b 减数
 * @returns {number} 返回正确相减后的数
 *
 * @example
 * subNum(0.3 , 0.2) // => 0.1
 */
export const subNum = (a: number, b: number) => {
  var c, d, e
  try {
    c = a.toString().split('.')[1].length
  } catch (f) {
    c = 0
  }
  try {
    d = b.toString().split('.')[1].length
  } catch (f) {
    d = 0
  }
  return (e = Math.pow(10, Math.max(c, d))), (mulNum(a, e) - mulNum(b, e)) / e
}

/**
 * 乘法运算（解决浮点数计算问题）
 * @param {number} a
 * @param {number} b
 * @returns {number} 返回正确相乘后的数
 *
 * @example
 * mulNum(0.3 , 1.5) // => 0.45
 */
export const mulNum = (a: number, b: number) => {
  var c = 0,
    d = a.toString(),
    e = b.toString()
  try {
    c += d.split('.')[1].length
  } catch (f) {}
  try {
    c += e.split('.')[1].length
  } catch (f) {}
  return (Number(d.replace('.', '')) * Number(e.replace('.', ''))) / Math.pow(10, c)
}

/**
 * 除法运算（解决浮点数计算问题）
 * @param {number} a 被除数
 * @param {number} b 除数
 * @returns {number} 返回正确相除后的数
 *
 * @example
 * divNum(0.3 , 0.1) // => 3
 */
export const divNum = (a: number, b: number) => {
  var c,
    d,
    e = 0,
    f = 0
  try {
    e = a.toString().split('.')[1].length
  } catch (g) {}
  try {
    f = b.toString().split('.')[1].length
  } catch (g) {}
  return (
    (c = Number(a.toString().replace('.', ''))),
    (d = Number(b.toString().replace('.', ''))),
    mulNum(c / d, Math.pow(10, f - e))
  )
}
